<html>
<head>
    <title>MAVLink Camera Manager</title>
    <meta charset="utf-8" />

    <style>
        :root {
            color-scheme: dark;
        }
    </style>
</head>

<body>
    <div id="app" style="display: flex; column-gap: 1em">
        <div style="display: flex; column-gap: 1em">
            <div v-for="item in content">
                <div>
                    <h3>Camera: {{ item.name }}</h3>
                </div>
                <div>
                    <p>Device: {{ item.source }}</p>
                </div>
                <h4>
                    Configure Stream:
                </h4>
                <div>
                    <streamform :device="item" :streams="streams" v-on:onconfigure="(value) => configureStream(value)">
                </div>
                <h4>
                    Controls:
                </h4>
                <div>
                    <button type="button" v-on:click="resetControls(item.source)">Reset controls</button>
                </div>
                <div v-for="control in item.controls">
                    <h5>Name: {{ control.name }}</h5>
                    <div v-if="control.configuration.Slider">
                        <v4lslider
                            :slider="control.configuration.Slider"
                            :name="control.id.toString()"
                            v-on:onchange="(value) => setControl(item.source, control.id, value)"
                        ></v4lslider>
                    </div>

                    <div v-if="control.configuration.Bool">
                        <input
                            type="checkbox"
                            :checked="control.configuration.Bool.value == 1"
                            @change="(event) => setControl(item.source, control.id, event.target.checked ? 1 : 0)"
                            >
                        <label>On</label>
                    </div>

                    <div v-if="control.configuration.Menu">
                        <select
                            @change="(event) => setControl(item.source, control.id, event.target.value)"
                        >
                            <option
                                v-for="option in control.configuration.Menu.options"
                                v-bind:value="option.value"
                                :selected="option.value == control.configuration.Menu.value"
                                >
                                {{option.name}}
                            </option>
                        </select>
                    </div>
                </div>
            </div>
            <div>
                <h3>Streams</h3>
                <button type="button" v-on:click="openWebsiteInTab('webrtc/index.html')">WebRTC website</button>
                <div v-for="stream in streams">
                    <div>
                        <h3>Name: {{ stream.video_and_stream.name }}</h3>
                    </div>
                    <div>
                        <p>Video: {{ getVideoDescription(stream.video_and_stream) }}
                        </p>
                    </div>
                    <div>
                        <button type="button" v-on:click="deleteStream(stream.video_and_stream.name)">Delete stream</button>
                    </div>
                    <div>
                        <p>Endpoints:</p>
                        <div style="margin-left: 0.5em;" v-for="endpoint in stream.video_and_stream.stream_information.endpoints">
                            <p>{{ endpoint }}</p>
                        </div>
                    </div>
                    <div>
                        <p>Configuration:</p>
                        <pre style="margin-left: 0.5em;">{{ JSON.stringify(stream, undefined, 2) }}</pre>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="vue.js"></script>
    <script>
        const app = Vue.createApp({
            mounted: function () {
                this.requestData()
            },
            methods: {
                openWebsiteInTab: function(url) {
                    window.open(url, '_blank')
                },
                getVideoDescription: function(video_and_stream) {
                    let response = ''
                    switch (video_and_stream.stream_information.configuration.type) {
                        case 'redirect': break
                        default: {
                            response += video_and_stream.stream_information.configuration.encode
                            + ' ' + video_and_stream.stream_information.configuration.width
                            + 'x' + video_and_stream.stream_information.configuration.height
                            + ' @ ' + video_and_stream.stream_information.configuration.frame_interval.denominator
                            + ' / ' + video_and_stream.stream_information.configuration.frame_interval.numerator + ' FPS, '
                        }
                    }
                    response += 'Thermal: ' + video_and_stream.stream_information.extended_configuration?.thermal ?? false
                    response += 'Disable Mavlink: ' + video_and_stream.stream_information.extended_configuration?.disable_mavlink ?? false
                    return response
                },
                requestData: async function() {
                    const response_content = await fetch('v4l')
                    this.content = await response_content.json()

                    const response_streams = await fetch('streams')
                    this.streams = await response_streams.json()
                },
                setControl: async function(source, id, value) {
                    console.log(`Configuring: source: ${source}, control_id: ${id}, value: ${value}`)
                    const settings = {
                        method: 'POST',
                        body: JSON.stringify({ "device": source, "v4l_id": Number(id), "value": Number(value) }),
                        headers: {
                            Accept: 'application/json',
                            'Content-Type': 'application/json',
                        }
                    }
                    const response = await fetch('v4l', settings)
                    this.checkResponse(response)
                },
                resetControls: async function(source) {
                    console.log(`Resetting: source: ${source} controls to its default values.`)
                    const settings = {
                        method: 'POST',
                        body: JSON.stringify({ "device": source }),
                        headers: {
                            Accept: 'application/json',
                            'Content-Type': 'application/json',
                        }
                    }
                    const response = await fetch('camera/reset_controls', settings)
                    this.checkResponse(response)
                },
                deleteStream: async function(stream_name) {
                    console.log(`Deleting stream: ${stream_name}`)

                    const url = new URL('delete_stream', window.location)
                    url.searchParams.set("name", stream_name)
                    const response = await fetch(url, {method: "DELETE"})
                    this.checkResponse(response).then(() => this.requestData())
                },
                checkResponse: async function(response) {
                    if (!response.ok) {
                        // To make the alert text more human readable, here we are:
                        //   1. removing the external double quotes pair, making the string `"text"` turns into `text`
                        //   2. unescaping new lines, making the string `text\\n newline` turns into `text\n newline`
                        //   3. unescaping double quotes, making the  string `text \\"quoted\\"` turns into `text \"quoted\"`
                        const text = await response.text().then((text) => text.replace(/^"(.+(?="$))"$/, '$1').replaceAll("\\n", "\n").replaceAll("\\\"", "\""))
                        console.warn(`Something went wrong: ${text}`)
                        alert(text)
                    } else {
                        const contentType = response.headers.get("content-type")
                        if(contentType && contentType.indexOf("application/json") !== -1) {
                            return await response.json()
                        }
                    }
                    return undefined
                },
                configureStream: async function(stream) {

                    const configuration = (() => {
                        switch (stream.source) {
                            case "Redirect": return {
                                "type": "redirect",
                            }
                            default: return {
                                "type": "video",
                                "encode": stream.configuration.encode,
                                "height": Number(stream.configuration.size.height),
                                "width": Number(stream.configuration.size.width),
                                "frame_interval": stream.configuration.interval,
                            }
                        }
                    })()

                    const content = {
                        "name": stream.name,
                        "source": stream.source,
                        "stream_information": {
                            "endpoints": stream.endpoints ? stream.endpoints.split(',') : ["udp://0.0.0.0:5600"],
                            "configuration": configuration,
                            "extended_configuration": {
                                "thermal": Boolean(stream.extended_configuration.thermal),
                                "disable_mavlink": Boolean(stream.extended_configuration.disable_mavlink),
                            },
                        }
                    }
                    console.log(`Configuring new strem: ${JSON.stringify(content, null, 2)}`)

                    const settings = {
                        method: 'POST',
                        body: JSON.stringify(content),
                        headers: {
                            Accept: 'application/json',
                            'Content-Type': 'application/json',
                        }
                    }
                    const response = await fetch('streams', settings)
                    this.checkResponse(response).then(() => this.requestData())
                },
            },
            data: function() {
                return {
                    "content": [],
                    "streams": []
                }
            }
        })

        app.component('v4lslider', {
            props: {
                slider: {
                    type: Object,
                    required: true
                },
                name: {
                    type: String,
                    required: true
                },
            },
            data: function() {
                return {
                    "val": this.slider.value,
                }
            },
            template: `
                <div>
                    <input type='range'
                        :min='slider.min'
                        :max='slider.max'
                        :value='val'
                        :step='slider.step'
                        :id="'range-input-' + name"
                        @change='$emit("onchange", val)'
                        v-on:input='val = $event.target.value'
                        class='slider'>
                    <span>{{val}}</span>
                </div>`
        })

        app.component('streamform', {
            props: {
                device: {
                    type: Object,
                    required: true
                },
                streams: {
                    type: Object,
                    required: true
                },
            },
            mounted: function() {
                this.stream_options.encoders = this.device.formats
                    .map((format) => this.encodeToStr(format.encode))
            },
            watch: {
                streams: {
                    handler: function(streams, _) {
                        this.stream = streams
                            .filter((stream) =>
                                stream.video_and_stream.video_source.Local && stream.video_and_stream.video_source.Local.device_path == this.device.source
                                || stream.video_and_stream.video_source.Gst && stream.video_and_stream.video_source.Gst.source.Fake == this.device.source)[0]
                        if(!this.stream) {
                            return
                        }

                        switch (this.stream.video_and_stream.stream_information.configuration.type) {
                            case "redirect": break
                            default: {
                                this.stream_setting.configuration.encode = this.stream.video_and_stream.stream_information.configuration.encode
                                this.stream_setting.configuration.size = {
                                    height: this.stream.video_and_stream.stream_information.configuration.height,
                                    width: this.stream.video_and_stream.stream_information.configuration.width
                                }
                                this.stream_setting.configuration.interval = this.stream.video_and_stream.stream_information.configuration.frame_interval
                            }
                        }

                        this.stream_setting.configuration.endpoints = this.stream.video_and_stream.stream_information.endpoints ?
                            this.stream.video_and_stream.stream_information.endpoints.join(", ") : ""
                        this.stream_setting.extended_configuration.thermal = Boolean(this.stream.video_and_stream.stream_information.extended_configuration?.thermal)
                        this.stream_setting.extended_configuration.disable_mavlink = Boolean(this.stream.video_and_stream.stream_information.extended_configuration?.disable_mavlink)
                    },
                    deep: true
                },
                stream_setting: {
                    handler: function(stream_setting, _) {
                        console.log(JSON.stringify(stream_setting, undefined, 2))

                        switch (stream_setting.configuration.type) {
                            case "redirect": break
                            default: {
                        this.stream_options.encoders =
                            this.device.formats
                                .map((format) => this.encodeToStr(format.encode))

                        this.stream_options.sizes =
                            this.device.formats
                                .filter((format) => this.encodeToStr(format.encode) == stream_setting.configuration.encode)
                                .map((format) => format.sizes)[0]
                                // Sort width by preference
                                ?.sort((size1, size2) => (10 * size2.width + size2.height) - (10 * size1.width + size1.height))

                        console.log(this.stream_options.sizes)

                        let chosen_size = stream_setting.configuration.size
                        if(chosen_size == undefined) {
                            return
                        }

                        this.stream_options.intervals =
                            this.stream_options.sizes
                                .filter((size) => size.width == chosen_size.width && size.height == chosen_size.height)[0].intervals

                            }
                        }
                    },
                    deep: true
                }
            },
            methods: {
                encodeToStr: function(encode) {
                    // encode is an enum that contains the encode as string or a object that describes the encode
                    return typeof(encode) == 'object' ? Object.values(encode)[0] : encode
                },
            },
            data: function() {
                return {
                    stream_setting: {
                        name: this.device.source + ' - ' + this.device.name,
                        source: this.device.source,
                        endpoints: undefined,
                        configuration: {
                            encode: undefined,
                            size: undefined,
                            interval: undefined,
                        },
                        extended_configuration: {
                            thermal: undefined,
                            disable_mavlink: undefined,
                        },
                    },
                    stream_options: {
                        encoders: undefined,
                        sizes: undefined,
                        intervals: undefined,
                    },
                    stream: undefined
                }
            },
            template: `
                <form
                >
                    <p>
                        <label>Name: </label>
                        <input
                            name="name"
                            type="text"
                            autocomplete="off"
                            v-model="stream_setting.name"
                        >
                    </p>

                    <div>
                        <label>Encode: </label>
                        <select
                            v-model="stream_setting.configuration.encode"
                            v-bind:disabled="stream_setting.source == 'Redirect'"
                        >
                            <option
                                v-for="encode in stream_options.encoders"
                                :value="encode"
                            >
                                {{ encode }}
                            </option>
                        </select>
                    </div>
                    <div>
                        <label>Size: </label>
                        <select
                            v-model="stream_setting.configuration.size"
                            v-bind:disabled="stream_setting.source == 'Redirect'"
                        >
                            <option
                                v-for="size in stream_options.sizes"
                                v-bind:value="{width: size.width, height: size.height}"
                            >
                                {{ size.width }} x {{ size.height }}
                            </option>
                        </select>
                    </div>
                    <div>
                        <label>FPS: </label>
                        <select
                            v-model="stream_setting.configuration.interval"
                            v-bind:disabled="stream_setting.source == 'Redirect'"
                        >
                            <option
                                v-for="interval in this.stream_options.intervals"
                                v-bind:value="interval"
                            >
                                {{ interval.denominator / interval.numerator }}
                            </option>
                        </select>
                    </div>
                    <div>
                        <label>Thermal: </label>
                        <input
                            type="checkbox"
                            v-model="stream_setting.extended_configuration.thermal"
                        >
                    </div>
                    <div>
                        <label>Disable Mavlink: </label>
                        <input
                            type="checkbox"
                            v-model="stream_setting.extended_configuration.disable_mavlink"
                        >
                    </div>

                    <p>
                        <label>Endpoints: </label>
                        <input
                            type="text"
                            autocomplete="off"
                            placeholder="udp://0.0.0.0:5600"
                            v-model="stream_setting.endpoints"
                        >
                    </p>
                    <button type="button" @click="$emit('onconfigure', stream_setting)">Configure stream</button>
                </form>
            `
        })

        app.mount("#app")
    </script>
</body>

</html>
